docs(skills): drop the issue id from the retirement guidance prescription - #15695
Conversation
…tion Convention 2 of the guidance-string conventions prescribed `(#issue[, ADR-XXXX Dn])`, so an author following it literally wrote an internal issue id into a `retiredKey()` tombstone — customer-facing spec text, where `check:doc-authoring` refuses one. The tombstone-note template a few lines up carried the same `(#<issue>)` fragment. Convention 2 now prescribes `(ADR-XXXX[ Dn])` and carries the redirect and the gate that enforces it: the id belongs in the schema comment. The template drops its fragment. Line-neutral at 337 of 337; the amended convention is 120 bytes, at the ratchet's per-line cap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
|
ACCEPT — governed in-seat review of PR #15695, head Implemented-by: Verified in a detached compare worktree at the PR head against merge-base The dev's open question — carry the two live schema-comment examples ( One mechanism refinement from the dev, accepted as the accurate reading: Landing regime: governed ( Generated by Claude Code |
Fixes #15388
Convention 2 of the retirement playbook prescribed an internal issue id inside the
retiredKey()tombstone string. An author following it literally writes that id intocustomer-facing spec text, which
check:doc-authoringrefuses — so the playbook's ownprescription could not be followed and stay green. The tombstone-note template a few
lines up carried the same fragment.
Site 1 — convention 2 of
### guidance 字符串怎么写before
after
The amended line carries the prescription, the redirect and the gate that enforces it —
and nothing else.
#⟨n⟩is the placeholder spelling the gate itself blesses (itsself-test pins "the
#⟨n⟩placeholder passes while the concrete ids it replaced stayred"), so the rule displays the very token it forbids without planting one.
Site 2 — the tombstone-note template
before
after
Nothing else on that line moves.
Line budget — line-neutral, no ceiling raise
Ratchet verdict lines, verbatim, at
83c73781e:337 lines before and after; two lines changed, none added. The amended convention line
measures 120 bytes — exactly
MAX_LINE_BYTES, which the gate compares withbytes(line) <= limit. No table row was touched (326 is unchanged, and its"lower the pin to 326" hint is pre-existing on
origin/main, not produced here).What did not fit, and why it is not here. The dispatch asked the redirect to also
cite
packages/spec/src/api/dispatcher.zod.ts:19andpackages/spec/src/api/plugin-rest-api.zod.ts:39as the two live examples. With theprescription code span costing 63 of the 120 bytes, 57 remain; the rule plus the gate
name plus the redirect already spend all 57, and the two paths cost ~60 more. A second
line was the only way to carry them, and every adjacent pair in that list merges to
122–238 bytes, so no line could be paid for by consolidation — and re-wrapping to buy a
line is refused as currency by the 2026-08-17 ruling. The example citations are
illustration rather than rule, so they yielded to the budget; they are recorded here
instead, where reviewers see them and no line ceiling applies.
Census, re-measured on this branch with the grep from the card
retiredKey()prescriptions citing an ADR onlyBoth issue-id hits are comment prose, not prescriptions —
dispatcher.zod.ts:19(adocblock) and
plugin-rest-api.zod.ts:39(a commented-out example): exactly the placethis change redirects the id to. My ADR total reads 87 where triage read 86; I
record the number I measured rather than adopting theirs, and it does not move the
conclusion — the load-bearing half is that zero live prescriptions carry an id.
One refinement to the card's mechanism
The card says the playbook "is a governed file (
check:doc-authoringscans.claude/),so the edit is checked by the same gate". That holds for the gate's bare-metadata-literal
rule, but not for its issue-id rule, which is scoped to
skills/**alone(
PUBLISHED_SKILLS_ROOT = 'skills'); the source says widening it to the internal roots"would be a different decision needing its own ruling". The gate's own output separates
the two populations:
So the playbook was never itself red — the defect is that it prescribed a shape that
goes red at the
retiredKey()site where it is applied (the gate's third rule, overpackages/speccustomer-facing text). The repair the card asks for is unchanged; onlythe reason it is not self-detecting is sharper. This is also why the id survives in the
two comment sites: the gate does not read comments there.
Gates
All 17 families derived by
dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwere run on the committed tree
83c73781e; each exit code was captured before any pipe.All 17 exited 0. Named verdicts:
node scripts/pm/check-governed-merges.mjs --test .claude/skills/spec-property-retirement/SKILL.md→ exit 3, GOVERNED:
.claude/**×1. Draft PR, human merge; no seat flips it ready,enqueues it, or arms auto-merge.
Local suite:
packages/spec/src/shared/retired-key-migrate-sentence.test.tsreads thisplaybook as one of its two judged corpora — run by path, 14 tests passed, including
"the retirement playbook and the published skill catalog agree with this pin" and
"anti-vacuity: the PLAYBOOK teaches BOTH shapes, so the scan judges at least two sites".
The retirement pin sweep (
retirement retired-key tombstone) ran 22 files / 130 tests,all passed.
pnpm lintwas narrowed rather than run repo-wide, and the narrowing is proven ratherthan assumed: eslint's own configuration resolution reports this file as outside its
population —
--format jsonon it returns one result,errorCount: 0, message"File ignored because no matching configuration was supplied". A markdown file eslint
never parses cannot move any other file's verdict, and no eslint config is in this diff
(1 file changed). CI runs the farm regardless.
Two gates first answered on the environment, not on the tree, and were re-run to a real
reading:
check-required-contexts --verify-required-setreturned exit 2 NOT VERIFIED(HTTP 401) until re-run with
NODE_OPTIONS=--use-env-proxy→ exit 0; andcheck:doc-formula-expressionsreturned exit 3 PREREQUISITE NOT MET until@objectstack/formulaand@objectstack/lintwere built → exit 0. Neither was recordedas a pass while unmeasured.
skip-changeset: this diff publishes nothing from any package — one file under.claude/**.Generated by Claude Code